#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/lexical_cast.hpp>

#include "help_api.h"

#include "hard_disk_controller.h"
#include "memory_map_file.h"

using namespace boost;
using namespace property_tree;

using namespace std;

void HardDiskController::init(uint baseAddress, uchar sectorsPerTrack,
                              uchar headCount, uchar cylinderHiCount,
                              uint sectorSize, uint hardDiskAddress,
                              uint dmaAddress)
{
    this->baseAddress       = baseAddress;
    this->sectorsPerTrack   = sectorsPerTrack;
    this->headCount         = headCount;
    this->cylinderHiCount   = cylinderHiCount;
    this->sectorSize        = sectorSize;
    this->hardDiskAddress   = hardDiskAddress;
    this->diskSize          = sectorSize * headCount * sectorsPerTrack *(cylinderHiCount * 0x100);
    this->dmaAddress        = dmaAddress;
    this->hardDiskfile      = shared_ptr<MemoryMappedFile>(new MemoryMappedFile("harddrive.raw", diskSize, hardDiskAddress, false));
}

HardDiskController::HardDiskController(Wrapper* wrap)
{
    this->wrap = wrap;
    
    // Read config from ini
    ptree pt;
    ini_parser::read_ini(CONFIG_FILE_NAME, pt);
    string BaseAddress = pt.get<string>(CONFIG_BASE_ADDRESS);
    unsigned int BaseAddressInt = strtol(BaseAddress.c_str(),0,0);
    
    string SectorPerTrack = pt.get<string>(CONFIG_SECTORS_PER_TRACK);
    unsigned int SectorPerTrackInt = strtol(SectorPerTrack.c_str(),0,0);    
    
    string HeadCount = pt.get<string>(CONFIG_HEAD_COUNT);
    unsigned int HeadCountInt = strtol(HeadCount.c_str(),0,0);    
    
    string CylinderHiCount = pt.get<string>(CONFIG_CYLINDER_HI_COUNT);
    unsigned int CylinderHiCountInt = strtol(CylinderHiCount.c_str(),0,0);    
    
    string SectorSize = pt.get<string>(CONFIG_SECTOR_SIZE);
    unsigned int SectorSizeInt = strtol(SectorSize.c_str(),0,0);   
    
    string HardDiskAddress = pt.get<string>(CONFIG_HARD_DISK_ADDRESS);
    unsigned int HardDiskAddressInt = strtol(HardDiskAddress.c_str(),0,0);      
    
    string DmaAddress = pt.get<string>(CONFIG_DMA_ADDRESS);
    unsigned int DmaAddressInt = strtol(DmaAddress.c_str(),0,0);   
    
    debug_print("Config.BaseAddress : 0x%02x\n",BaseAddressInt);
    debug_print("Config.SectorPerTrack : 0x%02x\n",SectorPerTrackInt);
    debug_print("Config.HeadCount : 0x%02x\n",HeadCountInt);
    debug_print("Config.CylinderHiCount : 0x%02x\n",CylinderHiCountInt);
    debug_print("Config.SectorSize : 0x%02x\n",SectorSizeInt);
    debug_print("Config.HardDiskAddress : 0x%02x\n",HardDiskAddressInt);
    debug_print("Config.DmaAddress : 0x%02x\n",DmaAddressInt);
    
    init(BaseAddressInt,SectorPerTrackInt,HeadCountInt,CylinderHiCountInt,SectorSizeInt,HardDiskAddressInt,DmaAddressInt);
}

HardDiskController::~HardDiskController()
{
    debug_print("~HardDiskController()\n");
}

void* HardDiskController::getAddress(){
    return getAddress(sector, head, cylinderHi, cylinderLow);
}

void* HardDiskController::getAddress(uchar sector, uchar head, uchar cylinderHi, uchar cylinderLow){
    uint headOffset = head * sectorSize * sectorsPerTrack * cylinderHiCount * 0x100;
    uint trackOffset = (cylinderHi * 0x100 + cylinderLow) * sectorSize * sectorsPerTrack;
    uint sectorOffset = sector * sectorSize;
    uint result = hardDiskAddress + headOffset + trackOffset + sectorOffset;
    //debug_print("head(%d), sectorsPerTrack(%d), cylinderHiCount(%d), headOffset(%02x), getAddress: %02x\n",head, sectorsPerTrack, cylinderHiCount, headOffset, result);
    return (void*)result;
}

void HardDiskController::outp(uint port, uchar value)
{
    //debug_print("outp(%d,%d)\n", port,value);
    uint reg = port - baseAddress;
    switch(reg)
    {
        case 0: // A0
            autoIncrement = value & 0x80;
            registerAddress = value & 0x3f;
            break;
        case 1: // A1
            switch(registerAddress)
            {
                case CYLINDER_HIGH:
                    if(value >= cylinderHiCount)
                        debug_print("hard_disk_controller.cpp: cylinderHi >= cylinderHiCount\n");
                    else
                        this->cylinderHi = value;
                    break;
                case CYLINDER_LOW:
                    this->cylinderLow = value;
                    break;    
                case HEAD:
                    if(value >= headCount)
                        debug_print("hard_disk_controller.cpp: head >= headCount\n");
                    else
                        this->head = value;
                    break;
                case SECTOR:
                    if(value >= sectorsPerTrack)
                        debug_print("hard_disk_controller.cpp: sector >= sectorsPerTrack\n");                    
                    else
                        this->sector = value;
                    break;
                case DISK_OP:
                    this->diskOp = value;
                    switch(value){
                        case OP_WRITE_DATA: 
                            //debug_print("Command write data\n");
                            //debug_print("Sector(%02x), Head(%02x), Cylinder(%02x%02x)\n",sector,head,cylinderHi,cylinderLow);
                            wrap->memRead(getAddress(), dmaAddress, sectorSize);                    
                            break;
                        case OP_READ_DATA:
                            //debug_print("Command read data\n");
                            //debug_print("Sector(%02x), Head(%02x), Cylinder(%02x%02x)\n",sector,head,cylinderHi,cylinderLow);
                            wrap->memWrite(dmaAddress, getAddress(), sectorSize);
                            break;
                        default:
                            debug_print("hard_disk_controller.cpp: Unhandled DISK_OP 0x%02x\n", value);                    
                            break;
                    }
                        
                    break;
                default:
                    debug_print("hard_disk_controller.cpp: outp have unhandled registerAddress = %02x\n", registerAddress);
                    break;
            }
            handleAutoInc();
            break;
    }
}

void HardDiskController::handleAutoInc()
{
    if(autoIncrement)
    {
        debug_print("hard_disk_controller.cpp: handleAutoInc enabled\n");
        registerAddress++;
    }
    if(registerAddress >= 0x40)
        registerAddress -= 0x40;
}

uchar HardDiskController::inp(uint port)
{
    uint reg = port - baseAddress;
    //debug_print("inp(0x%02X), reg(0x%02X)\n", port, reg);
    switch(reg)
    {
        case 0: //A0
            debug_print("hard_disk_controller.cpp: tried to read from A0, unhandled\n");
            break;
        case 1:
            switch(registerAddress)
            {
                case SECTORS_PER_TRACK:
                    return this->sectorsPerTrack;
                    break;
                case CYLINDER_HIGH:
                    return this->cylinderHi;
                    break;
                case CYLINDER_LOW:
                    return this->cylinderLow;
                    break;
                case HEAD:
                    return this->head;
                    break;
                case SECTOR:
                    return this->sector;
                    break;
                case DISK_OP:
                    return this->diskOp;
                    break;  
                default:
                    debug_print("hard_disk_controller.cpp: inp have unhandled registerAddress = %02x\n", registerAddress);
                    break;                    
            }
            handleAutoInc();
            break;
        default:
            debug_print("hard_disk_controller.cpp: inp have unhandled reg = %02x\n", reg);
            break;               
    }
    return 0;
}

